package com.google.code.stalkernet.async.factory;

import java.io.IOException;
import java.net.SocketAddress;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.code.stalkernet.async.io.AsyncSocketChannel;
import com.google.code.stalkernet.peer.Peer;
import com.google.code.stalkernet.reactor.Reactor;

public class AsyncSocketAddressPeerFactory <T extends Peer>
    implements AsyncFactory <SocketAddress, T>
{
    private final static Logger logger =
        Logger.getLogger (AsyncSocketAddressPeerFactory.class.getName ());

    private final AsyncFactory <? super AsyncSocketChannel, ? extends T>
        peerFactory;
    private final Reactor reactor;

    public AsyncSocketAddressPeerFactory (
        AsyncFactory <? super AsyncSocketChannel, ? extends T> peerFactory,
        Reactor reactor)
    {
        if (peerFactory == null)
            throw new IllegalArgumentException ("Peer factory is null");

        if (reactor == null)
            throw new IllegalArgumentException ("Reactor is null");

        this.peerFactory = peerFactory;
        this.reactor = reactor;
    }

    @Override
    public Handle produce (SocketAddress source,
        final Callback <? super T> callback)
    {
        if (source == null)
            throw new IllegalArgumentException ("Source is null");

        if (callback == null)
            throw new IllegalArgumentException ("Callback is null");

        SocketAddressWrapper socketAddressWrapper =
            new SocketAddressWrapper (source, callback);

        return socketAddressWrapper.start ();
    }

    private class SocketAddressWrapper implements Handle
    {
        private final SocketAddress socketAddress;
        private final Callback <? super T> callback;

        private Handle connectHandle = null;
        private Handle produceHandle = null;

        private AsyncSocketChannel asyncSocketChannel = null;

        private boolean done = false;

        public SocketAddressWrapper (
            SocketAddress socketAddress, Callback <? super T> callback)
        {
            if (socketAddress == null)
                throw new IllegalArgumentException ("Socket address is null");

            if (callback == null)
                throw new IllegalArgumentException ("Callback is null");

            this.socketAddress = socketAddress;
            this.callback = callback;
        }

        public Handle start ()
        {
            connect ();

            return done ? null : this;
        }

        @Override
        public boolean cancel ()
        {
            if (done) return false;
            else
            {
                if (connectHandle != null)
                    connectHandle.cancel ();

                if (produceHandle != null)
                    produceHandle.cancel ();

                if (asyncSocketChannel != null)
                {
                    try
                    {
                        asyncSocketChannel.close ();
                    }
                    catch (IOException ex)
                    {
                        logger.log (
                            Level.SEVERE,
                            "Cannot close async socket channel: " +
                                asyncSocketChannel,
                            ex);
                    }
                }

                done = true;
                return true;
            }
        }

        private void connect ()
        {
            try
            {
                asyncSocketChannel = AsyncSocketChannel.open (reactor);
                connectHandle = asyncSocketChannel.connect (
                    socketAddress,
                    new Callback <Void>()
                    {
                        @Override
                        public void onSuccess (Void result)
                        {
                            connectHandle = null;

                            producePeer ();
                        }

                        @Override
                        public void onError (Throwable error)
                        {
                            if (error == null)
                                throw new IllegalArgumentException (
                                    "Error is null");

                            connectHandle = null;

                            error (error);
                        }
                    });
            }
            catch (IOException ex)
            {
                error (ex);
            }
        }

        private void producePeer ()
        {
            produceHandle = peerFactory.produce (
                asyncSocketChannel,
                new Callback <T>()
                {
                    @Override
                    public void onSuccess (T result)
                    {
                        if (result == null)
                            throw new IllegalArgumentException (
                                "Result is null");

                        produceHandle = null;
                        asyncSocketChannel = null;
                        done = true;
                        callback.onSuccess (result);
                    }

                    @Override
                    public void onError (Throwable error)
                    {
                        if (error == null)
                            throw new IllegalArgumentException (
                                "Error is null");

                        produceHandle = null;
                        error (error);
                    }
                });
        }

        private void error (Throwable error)
        {
            if (error == null)
                throw new IllegalArgumentException ("Error is null");

            cancel ();
            callback.onError (error);
        }
    }
}
